﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Caching;
using Microsoft.CSharp;
using Resolve.Contracts;
using Resolve.FlagInfo;
using Resolve.Web;

namespace Resolve.Core
{
    public class CodeCompiler : Contracts.ICodeCompiler
    {

        /// <summary>
        /// 标志查找
        /// </summary>
        private static readonly IFlagFinder FlagFinder = new FlagFinderFromFile();

        /// <summary>
        /// 代码生成
        /// </summary>
        private static readonly CodeGeneraterBase CodeGeneraterBase = new CodeGenerater();

        /// <summary>
        /// 动态编译代码
        /// </summary>
        /// <param name="strCode">代码</param>
        /// <returns></returns>
        private static Assembly Compile(string strCode)
        {
            string code = strCode;
            if (string.IsNullOrEmpty(code))
            {
                return null;
            }
            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters parameter = new CompilerParameters
            {
                IncludeDebugInformation = false,
                TempFiles = new TempFileCollection(ResolveConfig.CompileSavePath),
                CompilerOptions = "/target:library /optimize",
                GenerateInMemory = false,
                GenerateExecutable = false
            };
            var assemblies =
                AppDomain.CurrentDomain.GetAssemblies().Select(a => a.Location).ToArray();
            parameter.ReferencedAssemblies.AddRange(assemblies);
            CompilerResults result = provider.CompileAssemblyFromSource(parameter, code);
            if (result.Errors.Count > 0)
            {
                StringBuilder sbError = new StringBuilder();
                foreach (CompilerError e in result.Errors)
                {
                    sbError.Append(e.ErrorNumber + ":" + e.ErrorText);
                }
                throw new Exception(sbError.ToString(), null);
            }
            Assembly assembly = result.CompiledAssembly;
            provider.Dispose();
            return assembly;
        }


        public FlagCodeInFile CompileFile(string source, IDictionary<string, object> viewData, IDictionary<string, object> model)
        {
            FlagCodeInFile flagCodeInFile;
            string className = ResolveConfig.GenerateClassNameByFilePath(source);
            object ojb = GetCache(className);
            if (ojb == null)
            {
                FlagFinder.Source = source;
                flagCodeInFile = FlagFinder.FindFlags();
                flagCodeInFile.ClassName = className;
                CodeGeneraterBase.FlagCodeInFile = flagCodeInFile;
                CodeInfo codeInfo = CodeGeneraterBase.CombineCode();
                List<string> lsUsing = codeInfo.LsUsing;
                AmendUsing2(ref lsUsing);
                string strUsing = lsUsing.Aggregate(string.Empty, (current, s) => current + s);
                strUsing += "namespace " + ResolveConfig.CompileNameSpace + "\r\n{\r\n";
                string code = strUsing + codeInfo.BodyCode + "\t\t}\r\n";
                object obj = Compile(code).CreateInstance(ResolveConfig.CompileNameSpace + "." + className, false, BindingFlags.Default, null, new object[] { viewData, model }, null, null);
                flagCodeInFile.CompileObject = obj;
                SetCache(flagCodeInFile, source, flagCodeInFile.ClassName);
                if (flagCodeInFile.CompileObject == null) return flagCodeInFile;
                ViewPageBase viewPageBase = (ViewPageBase)flagCodeInFile.CompileObject;
                viewPageBase.ViewData = viewData;
                viewPageBase.Model = model;
                flagCodeInFile.CompileObject = viewPageBase;
            }
            else
            {
                flagCodeInFile = (FlagCodeInFile)ojb;
                ViewPageBase viewPageBase = (ViewPageBase)flagCodeInFile.CompileObject;
                viewPageBase.ViewData = viewData;
                viewPageBase.Model = model;
                flagCodeInFile.CompileObject = viewPageBase;
            }
            return flagCodeInFile;
        }


        /// <summary>
        /// 编译目录下的文件根据文件路径
        /// </summary>
        /// <param name="source"></param>
        /// <param name="viewData"></param>
        /// <param name="model"></param>
        public FlagCodeInFile CompileFloderFromFilePath(string source, IDictionary<string, object> viewData, IDictionary<string, object> model)
        {
            //获取目录
            string folderPath = Path.GetDirectoryName(source);
            if (folderPath == null)
            {
                throw new NullReferenceException(source + "指定的文件夹不存在");
            }
            FlagCodeInFile flagCodeInFile = new FlagCodeInFile();
            //类名
            string newClassName = ResolveConfig.GenerateClassNameByFilePath(source).ToLower();
            //获取缓存
            object ojb = GetCache(newClassName);
            if (ojb == null)
            {
                //设置Dll辅助文件路径
                string dllHelperFile = ResolveConfig.CompileSavePath + folderPath.Replace(ResolveConfig.AppPath, "").Replace("\\", "_");
                //是否需要重新编译
                if (IsNeedToCompile(dllHelperFile, source))
                {
                    //获取该目录下的需要编译的文件
                    string[] files = Directory.GetFiles(folderPath, "*." + ResolveConfig.TemplateExe, SearchOption.TopDirectoryOnly);
                    StringBuilder sbBodyCode = new StringBuilder();
                    List<string> lsUsing = new List<string>();
                    foreach (string file in files)
                    {
                        FlagFinder.Source = file;
                        FlagCodeInFile flagCodeInFileTemp = FlagFinder.FindFlags();
                        flagCodeInFileTemp.ClassName = ResolveConfig.GenerateClassNameByFilePath(file);
                        if (file.ToLower().Equals(source.ToLower()))
                        {
                            flagCodeInFile = flagCodeInFileTemp;
                        }
                        if (flagCodeInFileTemp.LsCode.Count == 0)
                        {
                            continue;
                        }
                        CodeGeneraterBase.FlagCodeInFile = flagCodeInFileTemp;
                        CodeInfo codeInfo = CodeGeneraterBase.CombineCode();
                        if (codeInfo == null)
                        {
                            continue;
                        }
                        AmendUsing(ref lsUsing, codeInfo.LsUsing);
                        sbBodyCode.Append(codeInfo.BodyCode + "\r\n\r\n");
                    }
                    flagCodeInFile.ClassName = newClassName;
                    AmendUsing2(ref lsUsing);
                    string strUsing = lsUsing.Aggregate<string, string>(null, (current, s) => current + s);
                    strUsing += "namespace " + ResolveConfig.CompileNameSpace + "\r\n{\r\n";
                    Assembly assembly = Compile(strUsing + sbBodyCode + "\t\t}\r\n");
                    if (assembly == null)
                    {
                        flagCodeInFile.CompileObject = null;
                    }
                    else
                    {
                        string assemblyName = assembly.ManifestModule.Name.Replace(".dll", "");
                        StreamWriter sw = new StreamWriter(dllHelperFile);
                        sw.Write(assemblyName);
                        sw.Close();
                        flagCodeInFile.CompileObject =
                            assembly.CreateInstance(ResolveConfig.CompileNameSpace + "." + newClassName, true, BindingFlags.Default, null, new object[] { viewData, model }, null, null);
                    }
                }
                else
                {
                    StreamReader sr = new StreamReader(dllHelperFile);
                    string assemblyName = sr.ReadToEnd();
                    sr.Close();
                    FlagFinder.Source = source;
                    flagCodeInFile = FlagFinder.FindFlags();
                    Type type = Type.GetType(ResolveConfig.CompileNameSpace + "." + newClassName + "," + assemblyName);
                    if (type == null)
                    {
                        flagCodeInFile.CompileObject = null;
                    }
                    else
                    {
                        flagCodeInFile.CompileObject =
                            type.Assembly.CreateInstance(ResolveConfig.CompileNameSpace + "." + newClassName, true,
                                BindingFlags.Default, null, new object[] { viewData, model }, null, null);
                    }
                }
                SetCache(flagCodeInFile, source, newClassName);
                if (flagCodeInFile.CompileObject == null) return flagCodeInFile;
                ViewPageBase viewPageBase = (ViewPageBase)flagCodeInFile.CompileObject;
                viewPageBase.ViewData = viewData;
                viewPageBase.Model = model;
                flagCodeInFile.CompileObject = viewPageBase;
            }
            else
            {
                flagCodeInFile = (FlagCodeInFile)ojb;
                ViewPageBase viewPageBase = (ViewPageBase)flagCodeInFile.CompileObject;
                if (viewPageBase == null) return flagCodeInFile;
                viewPageBase.ViewData = viewData;
                viewPageBase.Model = model;
                flagCodeInFile.CompileObject = viewPageBase;
            }
            return flagCodeInFile;
        }

        /// <summary>
        /// 判断是否需要重新编译目录
        /// </summary>
        /// <returns></returns>
        private static bool IsNeedToCompile(string dllHelperFile, string source)
        {
            //文件不存在就返回True
            if (!File.Exists(dllHelperFile))
            {
                File.Create(dllHelperFile).Close();
                return true;
            }
            DateTime dtDllHelperFile = File.GetLastWriteTime(dllHelperFile);
            DateTime dtSource = File.GetLastWriteTime(source);
            bool idNeed = dtSource > dtDllHelperFile;
            if (!idNeed) return false;
            File.Delete(dllHelperFile);
            File.Create(dllHelperFile).Close();
            return true;
        }

        /// <summary>
        /// 更正Using
        /// </summary>
        /// <returns></returns>
        private static void AmendUsing(ref List<string> lsUsing, ICollection<string> newUsing)
        {
            if (newUsing == null || newUsing.Count == 0)
            {
                return;
            }
            if (lsUsing.Count == 0)
            {
                if (newUsing.Count > 0)
                {
                    lsUsing.AddRange(newUsing);
                }
            }
            else
            {
                foreach (string s in newUsing)
                {
                    if (!lsUsing.Contains(s))
                    {
                        lsUsing.Add(s);
                    }
                }
            }
        }

        /// <summary>
        /// 更正Using2
        /// </summary>
        /// <returns></returns>
        private static void AmendUsing2(ref List<string> lsUsing)
        {
            if (lsUsing.Count == 0)
            {
                lsUsing.Add("using System.Globalization;\r\n");
                lsUsing.Add("using System.Collections.Generic;\r\n");
                lsUsing.Add("using System.Text;\r\n");
                lsUsing.Add("using Resolve.Web;\r\n");
            }
            else
            {
                if (!lsUsing.Contains("using System.Globalization;\r\n"))
                {
                    lsUsing.Add("using System.Globalization;\r\n");
                }
                if (!lsUsing.Contains("using System.Collections.Generic;\r\n"))
                {
                    lsUsing.Add("using System.Collections.Generic;\r\n");
                }
                if (!lsUsing.Contains("using System.Text;\r\n"))
                {
                    lsUsing.Add("using System.Text;\r\n");
                }
                if (!lsUsing.Contains("using Resolve.Web;\r\n"))
                {
                    lsUsing.Add("using Resolve.Web;\r\n");
                }
            }
        }

        /// <summary>
        /// 获取缓存
        /// </summary>
        /// <param name="className"></param>
        /// <returns></returns>
        private static object GetCache(string className)
        {
            string keyName = "re_" + className;
            return HttpContext.Current.Cache.Get(keyName);
        }

        /// <summary>
        /// 设置缓存
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="filePath"></param>
        /// <param name="className"></param>
        private static void SetCache(FlagCodeInFile obj, string filePath, string className)
        {
            if (obj == null)
            {
                return;
            }
            FlagCodeInFile objCache = obj;
            string keyName = "re_" + className;
            CacheDependency cd;
            if (objCache.CompileObject != null)
            {
                ViewPageBase viewPageBase = (ViewPageBase)objCache.CompileObject;
                viewPageBase.ViewData = null;
                viewPageBase.Model = null;
                objCache.CompileObject = viewPageBase;
                if (!string.IsNullOrEmpty(viewPageBase.Layout))
                {
                    cd = new CacheDependency(new[] { filePath, HttpContext.Current.Server.MapPath(viewPageBase.Layout) });
                }
                else
                {
                    cd = new CacheDependency(filePath);
                }
            }
            else
            {
                cd = new CacheDependency(filePath);
            }
            if (HttpContext.Current.Cache.Get(keyName) != null)
            {
                HttpContext.Current.Cache.Remove(keyName);
            }
            HttpContext.Current.Cache.Insert(keyName, objCache, cd, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(30));
        }
    }
}
